package inabit.compiler.parser;

import inabit.compiler.lexical.LexicalAnalyzer;
import inabit.compiler.lexical.Token;
import inabit.compiler.parser.grammar.Grammar;
import inabit.compiler.parser.grammar.NonTerminal;
import inabit.compiler.parser.grammar.Production;
import inabit.compiler.parser.grammar.Symbol;
import inabit.compiler.parser.grammar.Terminal;
import inabit.compiler.parser.table.PredictiveTable;
import inabit.compiler.parser.table.TableBuilder;

import java.util.List;
import java.util.Stack;


/**Represents the syntactic and semantic analyzer, it analyzes the file
 * read by the lexical analyzer while execute the semantic actions when necessary*/
/**@author Rafael Rocha*/
public abstract class Parser<T extends SemanticAction> {
	
	private Token currentToken;
	private Stack<Symbol> symbolStack;
	private LexicalAnalyzer lexer;
	private PredictiveTable predTable;
	private Grammar grammar;
	private boolean printState;
	
	/**Receives the lexical analyzer and the context-free grammar of the language*/
	public Parser(LexicalAnalyzer lexer, Grammar grammar) {
		symbolStack = new Stack<Symbol>();
		this.lexer = lexer;
		this.grammar = grammar;
		this.predTable = new TableBuilder(grammar).buildPreditiveTable();
		printState = false;
	}
	public void setPrintCompilation(boolean print){
		this.printState = print;
	}
	/**Print the current state of the analysis by showing the current input token and 
	 * symbol stack state*/
	private void printState() {
		System.out.println("input: " + currentToken.getTokenClass() + "[" + currentToken.getLine() + ", " + currentToken.getColumn() + "]");
		System.out.print("stack: ");
		for(Symbol symbol : symbolStack) {
			if(symbol instanceof NonTerminal) {
				System.out.print(((NonTerminal)symbol).getLabel());
			}else if(symbol instanceof Terminal) {
				System.out.print(((Terminal)symbol).getTokenClass());
			}else if(symbol instanceof SemanticAction) {
				System.out.print("{" + ((SemanticAction)symbol).variable().getLabel() + "}");
			}
			System.out.print("  ");
		}
		System.out.println();
	}
	
	protected abstract void initialize();
	
	private void init() throws Exception {
		lexer.reset();	
		currentToken = lexer.nextToken();
		symbolStack.clear();
		symbolStack.push(new Terminal(lexer.eof()));
		symbolStack.push(grammar.getInitialSymbol());
		initialize();
	}
	
	public String errorMessage(String title, String error, int line, int col) {
		return title + " at [" + line + ", " + col + "]: " + error;
	}
	
	/**returns <i>true</i> if accepted the file or <i>false</i> otherwise*/
	public boolean analyze() throws Exception{
		try {
			init();
		} catch (Exception e) {
			throw new Exception(errorMessage("Lexical error", e.getMessage(), currentToken.getLine(), currentToken.getColumn()));
		}
		while(!symbolStack.isEmpty()) {
			if(printState) { printState(); }
			Symbol top = symbolStack.peek();
			if(top instanceof Terminal) {
				Terminal ter = (Terminal)top;
				//if the terminal at input is equals to the terminal on stack
				//pop symbol stack and update the current token
				if(ter.getTokenClass().equals(currentToken.getTokenClass())) {
					symbolStack.pop();
					ter.setValue(currentToken.getValue());
					try {
						currentToken = lexer.nextToken();
					} catch (Exception e) {
						throw new Exception(errorMessage("Lexical error", e.getMessage(), currentToken.getLine(), currentToken.getColumn()));
					}
				}else {//if the tokens doesn't match emits an error
					throw new Exception(
							errorMessage(
									"Syntax Error",
									ter.getTokenClass() + " expected",
									currentToken.getLine(),
									currentToken.getColumn()));
				}
			}else if(top instanceof NonTerminal) {
				NonTerminal nt = (NonTerminal)top;
				symbolStack.pop();
				Production r = predTable.getProduction(nt.getLabel(), currentToken.getTokenClass());
				//if didn't found a production rule to the situation, emits an error
				if(r == null) {
					throw new Exception(
							errorMessage(
									"Syntax Error",
									predTable.getErrorMessage(nt.getLabel(), currentToken.getTokenClass(), currentToken),
									currentToken.getLine(),
									currentToken.getColumn()));
				}else {//otherwise executes the rule and push the symbols of the production to symbol stack
					try {
						List<Symbol> symbols = r.derive(nt);
						for(int i = symbols.size()-1; i >= 0; i--) {
							symbolStack.push(symbols.get(i));
						}
					}catch(Exception e) {
						throw new Exception(
								errorMessage("Syntax Error",
										e.getMessage(),
										currentToken.getLine(),
										currentToken.getColumn()));
					}
				}
				
			}else if(top instanceof SemanticAction) {
				@SuppressWarnings("unchecked")
				T action = (T)top;
				
				try{
					initAction(action);
					//try to execute the semantic action and emits an error if an exception occurs
					action.execute();
					deinitAction(action);
					symbolStack.pop();
				}catch(Exception ex) {
					throw new Exception(
							errorMessage(
									"Semantic error",
									ex.getMessage(),
									currentToken.getLine(),
									currentToken.getColumn()));
				}
			}
		}
		return true;
	}
	public abstract void initAction(T action);
	public abstract void deinitAction(T action);

	public void setLexicalAnalyzer(LexicalAnalyzer lexer) {
		this.lexer = lexer;
	}
}
